package com.hui.platform.falseworkapi.common.web.handler;

import com.hui.platform.falseworkapi.common.core.constant.ResultCodeEnum;
import com.hui.platform.falseworkapi.common.core.constant.SystemConstant;
import com.hui.platform.falseworkapi.common.core.exception.BaseException;
import com.hui.platform.falseworkapi.common.core.exception.BusinessException;
import com.hui.platform.falseworkapi.common.core.vo.CommonResult;
import com.hui.platform.falseworkapi.common.web.annotation.ApiResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.MessageSource;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.validation.ConstraintViolationException;
import java.util.*;

/**
 * 全局API结果处理器（包括成功结果和异常处理）
 *
 * @author Peng
 * @date 2019/11/4
 */
@Slf4j
@ControllerAdvice
@ConditionalOnWebApplication
@ConditionalOnMissingBean(ExceptionHandler.class)
public class ApiResultGlobalHandler implements ResponseBodyAdvice<Object> {

    /**
     * Spring环境变量
     */
    private final Environment environment;

    private final MessageSource messageSource;

    @Autowired
    public ApiResultGlobalHandler(Environment environment, MessageSource messageSource) {
        this.environment = environment;
        this.messageSource = messageSource;
    }

    //-----------------------------------------------成功处理------------------------------------------------

    /**
     * 是否执行beforeBodyWrite
     *
     * @param returnType    返回类型
     * @param converterType 转换器类型
     * @return 是否执行beforeBodyWrite方法
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        //获取继承参数（声明类，定义的地方）
        ApiResult dca = AnnotationUtils.findAnnotation(returnType.getDeclaringClass(), ApiResult.class);
        //获取当前类的参数
        ApiResult cca = AnnotationUtils.findAnnotation(returnType.getContainingClass(), ApiResult.class);
        //获取当前方法
        ApiResult ma = returnType.getMethodAnnotation(ApiResult.class);
        //是否被包装(最近匹配法则）
        boolean isWrapped = false;
        if (dca != null) {
            isWrapped = dca.isWrapped();
        }
        if (cca != null) {
            isWrapped = cca.isWrapped();
        }
        if (ma != null) {
            isWrapped = ma.isWrapped();
        }
        return isWrapped;
    }

    /**
     * 根据注解重写返回的Body
     *
     * @param body                  返回体
     * @param returnType            返回类型
     * @param selectedContentType   选择类型
     * @param selectedConverterType 选择转换器类型
     * @param request               请求对象
     * @param response              响应对象
     * @return 包装的请求体
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        log.debug("beforeBodyWrite..start...");

        //获取继承参数（声明类，定义的地方）
        ApiResult dca = AnnotationUtils.findAnnotation(returnType.getDeclaringClass(), ApiResult.class);
        //获取当前类的参数
        ApiResult cca = AnnotationUtils.findAnnotation(returnType.getContainingClass(), ApiResult.class);
        //获取当前方法
        ApiResult ma = returnType.getMethodAnnotation(ApiResult.class);
        //自定义成功消息
        String customMsg = "";
        //从注解中获取相应的信息,优先级：方法、包含类、定义类。(最近匹配法则）
        if (dca != null && StringUtils.hasText(dca.value())) {
            customMsg = dca.value();
        }
        if (cca != null && StringUtils.hasText(cca.value())) {
            customMsg = cca.value();
        }
        if (ma != null && StringUtils.hasText(ma.value())) {
            customMsg = ma.value();
        }
        CommonResult<Object> result = CommonResult.build(ResultCodeEnum.SUCCESS, messageSource, customMsg, body);

        log.debug("result:{}", result);
        return result;
    }


    //-----------------------------------------------异常处理------------------------------------------------


    //------------------404-------------------------

    /**
     * Controller上一层相关异常
     *
     * @param e 异常
     * @return 异常信息
     */
    @ExceptionHandler({
            HttpRequestMethodNotSupportedException.class,
            HttpMediaTypeNotSupportedException.class,
            HttpMediaTypeNotAcceptableException.class,
            MissingPathVariableException.class,
            MissingServletRequestParameterException.class,
            ServletRequestBindingException.class,
            //ConversionNotSupportedException.class,
            TypeMismatchException.class,
            HttpMessageNotReadableException.class,
            HttpMessageNotWritableException.class,
            //MethodArgumentNotValidException.class,
            MissingServletRequestPartException.class,
            //BindException.class,
            //TODO NoHandlerFoundException的处理
            NoHandlerFoundException.class,
            AsyncRequestTimeoutException.class
    })
    @ResponseBody
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public Object handleServletException(Exception e) {
        log.info(e.getMessage(), e);
        return CommonResult.build(ResultCodeEnum.CLIENT_ERROR, messageSource, null, e.getMessage());
    }


    //------------------400-------------------------

    /**
     * 参数绑定校验异常
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler(value = BindException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Object handleBindException(BindException e) {
        log.info(e.getMessage(), e);
        return CommonResult.build(ResultCodeEnum.VALIDATE_FAILED, messageSource,null, wrapperBindingResult(e.getBindingResult()));
    }

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Object handleValidException(MethodArgumentNotValidException e) {
        log.info(e.getMessage(), e);
        return CommonResult.build(ResultCodeEnum.VALIDATE_FAILED, messageSource, null, wrapperBindingResult(e.getBindingResult()));
    }

    @ExceptionHandler(value = ConstraintViolationException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Object handleConstraintViolationException(ConstraintViolationException e) {
        List<String> errors = new ArrayList<>();
        e.getConstraintViolations().forEach(t -> errors.add(t.getRootBeanClass().getSimpleName() + ":" + t.getPropertyPath() + ":" + t.getMessage()));
        log.info(e.getMessage(), e);
        return CommonResult.build(ResultCodeEnum.VALIDATE_FAILED, messageSource, null, errors);
    }

    @ExceptionHandler(value = ConversionNotSupportedException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Object handleConversionNotSupportedException(ConversionNotSupportedException e) {
        log.info(e.getMessage(), e);
        return CommonResult.build(ResultCodeEnum.VALIDATE_FAILED, messageSource, null, e.getMessage());
    }


    //------------------500-------------------------


    /**
     * 自定义异常
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler(value = BaseException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Object handleBaseException(BaseException e) {
        log.info(e.getMessage(), e);
        String i18nString = e.getI18nMessage().getI18nString(messageSource);
        //返回前端操作码--根据i18n的key确定操作码
        Map<String, String> resultMap = new HashMap<>(2);
        resultMap.put("key",e.getI18nMessage().getKey());
        resultMap.put("message",i18nString);
        return CommonResult.build(ResultCodeEnum.CUSTOM_FAILED, messageSource, null, resultMap);
    }

    /**
     * 业务异常
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler(value = BusinessException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Object handleBusinessException(BusinessException e) {
        log.info(e.getMessage(), e);
        //业务异常国际化处理
        String i18nString = e.getI18nMessage().getI18nString(messageSource);
        return CommonResult.build(ResultCodeEnum.BUSINESS_FAILED, messageSource, null, i18nString);
    }

    @ExceptionHandler(value = IllegalArgumentException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Object handleBusinessException(IllegalArgumentException e) {
        log.info(e.getMessage(), e);
        //业务异常国际化处理 -- 这里没有
        String i18nString = e.getMessage();
        return CommonResult.build(ResultCodeEnum.BUSINESS_FAILED, messageSource, i18nString, i18nString);
    }

    /**
     * 未知异常
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Object handleException(Exception e) {

        //放行未认证权限的异常（上层框架处理）
        if (e instanceof AccessDeniedException) {
            throw (AccessDeniedException) e;
        }

        log.error(e.getMessage(), e);

        //生产环境隐藏错误信息
        String errorMsg = e.getMessage();
        if (Arrays.asList(environment.getActiveProfiles()).contains(SystemConstant.ENV_PROD)) {
            errorMsg = null;
        }
        return CommonResult.build(ResultCodeEnum.UNKNOWN_FAILED, messageSource, null, errorMsg);
    }


    //-----------------------------------------------私有方法区------------------------------------------------

    /**
     * 包装校验信息（一条String） -- 国际化校验器已进行处理
     *
     * @param bindingResult 绑定解决结果集
     * @return 错误信息
     */
    private String wrapperBindingResult(BindingResult bindingResult) {
        //包装绑定异常结果
        StringBuilder msg = new StringBuilder();
        for (ObjectError error : bindingResult.getAllErrors()) {
            msg.append(", ");
            if (error instanceof FieldError) {
                msg.append(((FieldError) error).getField()).append(": ");
            }
            msg.append(error.getDefaultMessage() == null ? "" : error.getDefaultMessage());

        }
        return msg.toString();
    }

}
